package com.wdm.diffutils.util;




import com.wdm.diffutils.diff.*;
import com.wdm.diffutils.marker.Entity;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Description:
 *
 * @author wudaiming
 * @date 2023/10/26 12:38
 */
public class DiffUtils {
    
    /**
     * Compares two objects field by field and returns their differences in an EntityDiff object.
     *
     * @param snapshot The snapshot object.
     * @param object   The current object.
     * @param <T>      The type of objects being compared.
     * @return EntityDiff containing the differences between the two objects.
     * @throws IllegalAccessException If the Field.get() method fails.
     */
    public static <T> EntityDiff diff(T snapshot, T object) throws IllegalAccessException {
        // Ensure the objects are of the same type
        if (!snapshot.getClass().equals(object.getClass())) {
            throw new IllegalArgumentException("Objects must be of the same type");
        }
        
        Field[] fields = snapshot.getClass().getDeclaredFields();
        EntityDiff diff = new EntityDiff();
        
        for (Field field : fields) {
            field.setAccessible(true);
            Object oldValue = field.get(snapshot);
            Object newValue = field.get(object);
            Diff fieldDiff = doDiff(field.getType(), oldValue, newValue);
            if (fieldDiff != null) {
                diff.addDiff(field.getName(), fieldDiff);
            }
        }
        
        diff.setSelfModified(!diff.getDiffs().isEmpty());
        
        return diff;
    }
    
    /**
     * Performs a difference check between two objects.
     *
     * @param type   The class type of the objects.
     * @param oldObj The old object.
     * @param newObj The new object.
     * @return A Diff object if differences are found, otherwise null.
     */
    private static Diff doDiff(Class<?> type, Object oldObj, Object newObj) {
        // if (Objects.isNull(oldObj) || Objects.isNull(newObj)) {
        // return null;
        // }
        if (oldObj instanceof Object[] && newObj instanceof Object[]) {
            return doArrayDiff((Object[]) oldObj, (Object[]) newObj);
        }
        // Handle lists
        if (type.equals(List.class)) {
            return doListDiff((List<?>) oldObj, (List<?>) newObj);
        }
        
        if (isDiff(oldObj, newObj)) {
            return new SingleDiff(oldObj, newObj);
        }
        return null;
    }

    private static Diff doArrayDiff(Object[] oldArray, Object[] newArray) {
        ListDiff diffs = new ListDiff();
        diffs.setOldValue(Arrays.asList(oldArray));
        diffs.setNewValue(Arrays.asList(newArray));

        int minLength = Math.min(oldArray.length, newArray.length);

        for (int i = 0; i < minLength; i++) {
            Object oldItem = oldArray[i];
            Object newItem = newArray[i];

            if (oldItem instanceof Object[] && newItem instanceof Object[]) {
                // 如果元素是嵌套数组，则递归调用doArrayDiff方法进行比较
                Diff nestedDiff = doArrayDiff((Object[]) oldItem, (Object[]) newItem);
                if (nestedDiff != null) {
                    diffs.addDiff(nestedDiff);
                }
            } else if (!Objects.equals(oldItem, newItem)) {
                // 如果元素不相等，则创建SingleDiff对象并添加到ListDiff中
                SingleDiff singleDiff = new SingleDiff(oldItem, newItem);
                singleDiff.setType(DiffType.MODIFIED);
                diffs.addDiff(singleDiff);
            }
        }

        // 处理剩余的元素，如果一个数组比另一个数组长，则将剩余的元素视为新增或删除的元素
        if (oldArray.length > minLength) {
            for (int i = minLength; i < oldArray.length; i++) {
                SingleDiff singleDiff = new SingleDiff(oldArray[i], null);
                singleDiff.setType(DiffType.REMOVED);
                diffs.addDiff(singleDiff);
            }
        } else if (newArray.length > minLength) {
            for (int i = minLength; i < newArray.length; i++) {
                SingleDiff singleDiff = new SingleDiff(null, newArray[i]);
                singleDiff.setType(DiffType.ADDED);
                diffs.addDiff(singleDiff);
            }
        }

        return diffs.size() > 0 ? diffs : null;
    }

    /**
     * Performs a difference check between two lists.
     * 在DDD中应该不考虑非Entity的类型，所以后面一个if可以删除
     * @param oldList The old list.
     * @param newList The new list.
     * @return A ListDiff object if differences are found, otherwise null.
     */
    private static Diff doListDiff(List<?> oldList, List<?> newList) {
        ListDiff diffs = new ListDiff();
        diffs.setOldValue(oldList);
        diffs.setNewValue(newList);
        
        if (oldList == null && newList == null) {
            return null;
        }
        // Get the first non-null list and check its first element
        boolean isEntityType = false;
        if (oldList != null && !oldList.isEmpty()) {
            isEntityType = oldList.get(0) instanceof Entity;
        } else if (newList != null && !newList.isEmpty()) {
            isEntityType = newList.get(0) instanceof Entity;
        }
        // 检查第一个元素是否为Entity类型
        // boolean isEntityType = oldList.size() > 0 && oldList.get(0) instanceof Entity;
        
        if (isEntityType) {
            handleEntityType(oldList, newList, diffs);
        } else {
            // todo 后续不再支持此模式
            handleNonEntityType(oldList, newList, diffs);
        }
        return diffs.size() > 0 ? diffs : null;
    }
    
    /**
     * 处理元素类型为Entity的列表，并找出两个列表之间的差异。
     * 将找到的差异以SingleDiff对象的形式添加到提供的ListDiff对象中。
     *
     * @param oldList 旧列表，包含Entity类型的元素
     * @param newList 新列表，包含Entity类型的元素
     * @param diffs   用于存储找到的差异的ListDiff对象
     */
    private static void handleEntityType(List<?> oldList, List<?> newList, ListDiff diffs) {
        if (oldList == null && newList != null) {
            newList.forEach(obj -> addDiff(diffs, null, obj, DiffType.ADDED));
            return;
        }
        
        if (newList == null && oldList != null) {
            oldList.forEach(obj -> addDiff(diffs, obj, null, DiffType.REMOVED));
            return;
        }
        
        // 创建一个Map，以存储oldList中的元素，使用它们的标识符作为键。
        // 这对于快速查找具有特定标识符的旧元素非常有用。
        Map<Object, Object> oldItemsByIdentifier = oldList != null ? oldList.stream()
                .collect(Collectors.toMap(DiffUtils::getIdentifier, Function.identity())) : new HashMap<>();
        
        if (newList != null) {
            for (Object newItem : newList) {
                Object newIdentifier = getIdentifier(newItem);
                Object oldItem = oldItemsByIdentifier.remove(newIdentifier);
                if (oldItem == null) {
                    addDiff(diffs, null, newItem, DiffType.ADDED);
                } else if (isDiff(oldItem, newItem)) {
                    addDiff(diffs, oldItem, newItem, DiffType.MODIFIED);
                }
            }
        }
        
        oldItemsByIdentifier.values().forEach(oldItem -> addDiff(diffs, oldItem, null, DiffType.REMOVED));
        
        // 遍历newList中的每个元素
        /*
         * for (Object newItem : newList) { Object newIdentifier = getIdentifier(newItem);
         * 
         * // 检查oldItemsByIdentifier Map中是否存在具有相同标识符的旧元素 if (!oldItemsByIdentifier.containsKey(newIdentifier)) { // 如果没有找到对应的旧元素，说明这是一个新增的元素。 // 创建一个SingleDiff对象，将其类型设置为ADDED，并将其添加到diffs中。 SingleDiff
         * singleDiff = new SingleDiff(null, newItem); singleDiff.setType(DiffType.ADDED); diffs.addDiff(singleDiff); } else { // 如果找到了对应的旧元素，则检查新旧元素之间是否存在差异。 Object oldItem =
         * oldItemsByIdentifier.get(newIdentifier); if (isDiff(oldItem, newItem)) { // 如果存在差异，则创建一个SingleDiff对象，将其类型设置为MODIFIED，并将其添加到diffs中。 SingleDiff singleDiff = new SingleDiff(oldItem, newItem);
         * singleDiff.setType(DiffType.MODIFIED); diffs.addDiff(singleDiff); } // 从Map中移除已处理的旧元素，以便稍后检查哪些旧元素已被删除。 oldItemsByIdentifier.remove(newIdentifier); } }
         * 
         * // 处理剩余的旧元素，这些元素在新列表中没有对应的项，因此被视为已删除的项。 for (Object oldItem : oldItemsByIdentifier.values()) { SingleDiff singleDiff = new SingleDiff(oldItem, null); singleDiff.setType(DiffType.REMOVED);
         * diffs.addDiff(singleDiff); addDiff(diffs, oldItem, null, DiffType.REMOVED); }
         */
    }
    
    private static void addDiff(ListDiff diffs, Object oldItem, Object newItem, DiffType type) {
        SingleDiff singleDiff = new SingleDiff(oldItem, newItem);
        singleDiff.setType(type);
        diffs.addDiff(singleDiff);
    }
    
    /**
     * 处理元素类型非Entity的列表，并找出两个列表之间的差异。
     * 将找到的差异以SingleDiff对象的形式添加到提供的ListDiff对象中。
     *
     * @param oldList 旧列表，包含非Entity类型的元素
     * @param newList 新列表，包含非Entity类型的元素
     * @param diffs   用于存储找到的差异的ListDiff对象
     */
    private static void handleNonEntityType(List<?> oldList, List<?> newList, ListDiff diffs) {
        // 遍历oldList和newList的元素
        for (int i = 0; i < oldList.size() || i < newList.size(); i++) {
            if (i < oldList.size() && i < newList.size()) {
                // 如果当前索引在oldList和newList的范围内，则比较相应的元素
                Object oldItem = oldList.get(i);
                Object newItem = newList.get(i);
                
                // 如果元素不相等，说明它们已经被修改。创建一个SingleDiff对象，设置类型为MODIFIED，并添加到diffs中。
                if (!Objects.equals(oldItem, newItem)) {
                    SingleDiff singleDiff = new SingleDiff(oldItem, newItem);
                    singleDiff.setType(DiffType.MODIFIED);
                    diffs.addDiff(singleDiff);
                }
            } else if (i < oldList.size()) {
                // 如果当前索引在oldList的范围内，但不在newList的范围内，则该元素已被删除。
                // 创建一个SingleDiff对象，设置类型为REMOVED，并添加到diffs中。
                SingleDiff singleDiff = new SingleDiff(oldList.get(i), null);
                singleDiff.setType(DiffType.REMOVED);
                diffs.addDiff(singleDiff);
            } else {
                // 如果当前索引在newList的范围内，但不在oldList的范围内，则该元素是新添加的。
                // 创建一个SingleDiff对象，设置类型为ADDED，并添加到diffs中。
                SingleDiff singleDiff = new SingleDiff(null, newList.get(i));
                singleDiff.setType(DiffType.ADDED);
                diffs.addDiff(singleDiff);
            }
        }
    }
    
    /**
     * get Identifier
     * @param entity entity Object
     */
    private static Object getIdentifier(Object entity) {
        // 如果对象是Entity类型，返回其ID作为标识符，否则返回null
        if (entity instanceof Entity) {
            return ((Entity<?>) entity).getId();
        }
        return null;
    }
    
    /**
     * Check if the provided objects are different.
     *
     * @param oldObj the first object to be compared
     * @param newObj the second object to be compared
     * @return true if the objects are different, false otherwise
     */
    private static boolean isDiff(Object oldObj, Object newObj) {
        // Return true if the objects are different, false otherwise.
        // Objects.equals handles null values.
        return !Objects.equals(oldObj, newObj);
    }
}
